/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "VelocityEvent.h"
#include <math/utl.h>

#define _USE_MATH_DEFINES
#include <math.h>
#include <xml/Saver.h>

namespace liba
{
	namespace lib3d
	{
		namespace particles
		{
			VelocityEvent::VelocityEvent() : _time(0), _relative(true), _randomUsed(false), _useRotate(false), _affectedByRotation(false)
			{
			}

			VelocityEvent::~VelocityEvent()
			{
			}
			
			VelocityEvent::VelocityRandom * VelocityEvent::get_random_for_particle(Particle * particle)
			{
				if(_randomUsed)
				{
					// try to get random
					VelocityRandom * random = static_cast<VelocityRandom*>(particle->get_data_for_event(this));
					
					// check if we need setup randomization for this keyframe
					if(random == NULL)
					{
						// setup random rotation
						random = (VelocityRandom*)malloc(sizeof(VelocityRandom));
						if(_useRotate)
						{
							random->random    = Matrix::rotate(math::random(_randomMin, _randomMax));
							random->useRotate = true;
							random->value     = random->random * _velocity;
						}
						else
						{
							random->random2   = math::random(_randomMin, _randomMax);
							random->useRotate = false;
							random->value     = random->random2 + _velocity;
						}
						
						// save data
						particle->set_data_for_event(this, random, false);
					}
					
					return random;
				}
				
				return NULL;
			}

			void VelocityEvent::set_velocity(const math::Vector3<Float> & velocity)
			{
				_velocity = velocity;
			}

			math::Vector3<Float> VelocityEvent::get_velocity()
			{
				return _velocity;
			}

			void VelocityEvent::set_time(float value)
			{
				_time = value;
			}
			
			math::Vector3<Float> VelocityEvent::get_velocity_for_particle(Particle * particle, KeyFrameIterator base_frame, KeyFrameArray * frames)
			{
				// get base velocity
				math::Vector3<Float> velocity = _velocity;
				
				// random for this frame
				VelocityRandom * random = get_random_for_particle(particle);
				
				// if this node relative to parents, search random for parents
				if(random == NULL && _relative && base_frame != frames->begin())
				{
					// search in all parents
					KeyFrameIterator itr = base_frame;
					while(true)
					{
						// try to get random for current frame
						random = itr->get_event<VelocityEvent>()->get_random_for_particle(particle);
						if(random != NULL)
						{
							// create random for this keyframe
							VelocityRandom * randomCurrent = (VelocityRandom*)malloc(sizeof(VelocityRandom));
							randomCurrent->random          = random->random;
							randomCurrent->random2         = random->random2;
							randomCurrent->useRotate       = random->useRotate;
							if(randomCurrent->useRotate)
							{
								randomCurrent->value = random->random * _velocity;
							}
							else
							{
								randomCurrent->value = random->random2 + _velocity;
							}
							
							// save data
							particle->set_data_for_event(this, randomCurrent, false);
							
							// reset random pointer
							random = randomCurrent;
							break;
						}
						
						// if we at the end
						if(itr == frames->begin())
							break;
						
						// next frame
						--itr;
					}
				}
				
				// apply random
				if(random != NULL)
					velocity = random->value;
				
				// return result velocity
				return velocity;
			}
			
			void VelocityEvent::update_particle(Particle * particle, Float delta_time, KeyFrameIterator base_frame, KeyFrameIterator next_frame, KeyFrameArray * frames)
			{
				// check if we need update base frame velocity in particle
				if(particle->baseVelocityFrame != base_frame->time)
				{
					particle->baseVelocity      = get_velocity_for_particle(particle, base_frame, frames);
					particle->baseVelocityFrame = base_frame->time;
				}
					
				// current velocity vector
				math::Vector3<Float> velocity = particle->baseVelocity;
				
				// if this frame interpolates with other
				if(next_frame != frames->end() && next_frame->get_event<VelocityEvent>()->_relative)
				{
					// check if we need update next frame velocity in particle
					if(particle->nextVelocityFrame != next_frame->time)
					{
						particle->nextVelocity      = next_frame->get_event<VelocityEvent>()->get_velocity_for_particle(particle, next_frame, frames);
						particle->nextVelocityFrame = next_frame->time;
					}
					
					// compute interpolation factor
					Float factor = (particle->lifeTime - base_frame->time) / (next_frame->time - base_frame->time);
					
					// iterpolate
					velocity = math::lerp(velocity, particle->nextVelocity, factor);
				}

				// if affected by rotation
				if(_affectedByRotation)
					velocity = Matrix::rotate(lib3d::Vector(0.0f, 0.0f, particle->currentState.angle)) * velocity;
				
				// upadte particle position
				particle->currentState.position += velocity * delta_time;
			}

			Atom VelocityEvent::get_type()
			{
				static Atom type("VELOCITY");
				return type;
			}
			
			Float VelocityEvent::get_time()
			{
				return _time;
			}

			void VelocityEvent::on_save_attributes(xml::SaverBase * saver) const
			{
				if(_time > 0.0f)
					saver->save_attribute("Time", _time);

				if(_velocity != lib3d::Vector())
					saver->save_attribute("Value", _velocity);

				if(!_relative)
					saver->save_attribute("Relative", _relative);

				if(_useRotate)
					saver->save_attribute("RotationRandom", _useRotate);

				if(_affectedByRotation)
					saver->save_attribute("AffectedByRotation", _affectedByRotation);

				if(_useRotate)
				{
					lib3d::Vector randomMin;
					randomMin.x = _randomMin.x / M_PI * 180.0f;
					randomMin.y = _randomMin.y / M_PI * 180.0f;
					randomMin.z = _randomMin.z / M_PI * 180.0f;

					lib3d::Vector randomMax;
					randomMax.x = _randomMax.x / M_PI * 180.0f;
					randomMax.y = _randomMax.y / M_PI * 180.0f;
					randomMax.z = _randomMax.z / M_PI * 180.0f;

					if(randomMin != lib3d::Vector())
						saver->save_attribute("RandomMin", randomMin);

					if(randomMax != lib3d::Vector())
						saver->save_attribute("RandomMax", randomMax);
				}
				else
				{
					if(_randomMin != lib3d::Vector())
						saver->save_attribute("RandomMin", _randomMin);

					if(_randomMax != lib3d::Vector())
						saver->save_attribute("RandomMax", _randomMax);
				}
			}
			
			bool VelocityEvent::on_attribute(xml::Provider * provider, const std::string & name, const std::string & value)
			{
				// velocity vector
				if(name == "Value")
					return converter::convert(value, &_velocity);
				
				// frame time
				if(name == "Time")
					return converter::convert(value, &_time);
				
				// if velocity relative to previous frame
				if(name == "Relative")
					return converter::convert(value, &_relative);
				
				// random type
				if(name == "RotationRandom")
					return converter::convert(value, &_useRotate);
				
				// if should be affected by rotation
				if(name == "AffectedByRotation")
					return converter::convert(value, &_affectedByRotation);
				
				// extract velocity random
				if(name == "RandomMin")
				{
					if(converter::convert(value, &_randomMin))
					{
						// convert to radians
						if(_useRotate)
						{
							_randomMin.x = _randomMin.x / 180.0f * M_PI;
							_randomMin.y = _randomMin.y / 180.0f * M_PI;
							_randomMin.z = _randomMin.z / 180.0f * M_PI;
						}
						
						_randomUsed = true;
						return true;
					}
					
					return false;
				}
				
				if(name == "RandomMax")
				{
					if(converter::convert(value, &_randomMax))
					{
						// convert to radians
						if(_useRotate)
						{
							_randomMax.x = _randomMax.x / 180.0f * M_PI;
							_randomMax.y = _randomMax.y / 180.0f * M_PI;
							_randomMax.z = _randomMax.z / 180.0f * M_PI;
						}
						
						_randomUsed = true;
						return true;
					}
					
					return false;
				}
				
				// unknown attribute
				return false;
			}
		}
	}
}